package com.gobang.websocket.session;

import com.gobang.util.RedisUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.time.Instant;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * WebSocket 会话管理组件
 *
 * 维护 userId -> WebSocketSession 与 sessionId -> UserSessionInfo 映射，
 * 并与 Redis 中的 ws:session:{sessionId}、online:user:{userId} 键保持同步。
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class WebSocketSessionManager {

    private static final long SESSION_TTL_SECONDS = 60L;
    private static final long ONLINE_TTL_SECONDS = 30L;

    private final RedisUtil redisUtil;

    /**
     * 当前在线用户的会话映射（单端在线：每个 userId 仅对应一个会话）。
     */
    private final Map<Long, WebSocketSession> userSessions = new ConcurrentHashMap<>();

    /**
     * 会话信息映射：sessionId -> UserSessionInfo。
     */
    private final Map<String, UserSessionInfo> sessionInfoMap = new ConcurrentHashMap<>();

    public void registerSession(Long userId, WebSocketSession session) {
        if (userId == null || session == null) {
            return;
        }
        String sessionId = session.getId();

        // 单端在线：如存在旧会话，则先关闭旧会话
        WebSocketSession oldSession = userSessions.put(userId, session);
        if (oldSession != null && oldSession.isOpen() && !oldSession.getId().equals(sessionId)) {
            try {
                log.info("检测到同一用户已有旧连接，关闭旧连接: userId={}, oldSessionId={}", userId, oldSession.getId());
                oldSession.close(CloseStatus.NORMAL);
            } catch (IOException e) {
                log.warn("关闭旧 WebSocket 连接失败: userId={}, sessionId={}", userId, oldSession.getId(), e);
            }
            // 清理旧会话在内存与 Redis 中的记录
            sessionInfoMap.remove(oldSession.getId());
            String oldSessionKey = buildSessionKey(oldSession.getId());
            redisUtil.delete(oldSessionKey);
        }

        //建立新会话连接
        Instant now = Instant.now();
        UserSessionInfo info = UserSessionInfo.builder()
                .userId(userId)
                .sessionId(sessionId)
                .connectedAt(now)
                .lastHeartbeat(now)
                .build();
        sessionInfoMap.put(sessionId, info);

        // 同步 Redis: ws:session:{sessionId}
        String sessionKey = buildSessionKey(sessionId);
        redisUtil.hSet(sessionKey, Map.of(
                "user_id", String.valueOf(userId),
                "connected_at", String.valueOf(now.toEpochMilli()),
                "last_heartbeat", String.valueOf(now.toEpochMilli())
        ), SESSION_TTL_SECONDS);

        // 刷新在线状态 online:user:{userId}
        String onlineKey = buildOnlineKey(userId);
        redisUtil.set(onlineKey, "1", ONLINE_TTL_SECONDS);

        log.info("注册 WebSocket 会话成功: userId={}, sessionId={}", userId, sessionId);
    }

    public void removeSession(WebSocketSession session) {
        if (session == null) {
            return;
        }
        removeSession(session.getId());
    }

    public void removeSession(String sessionId) {
        if (sessionId == null) {
            return;
        }
        UserSessionInfo info = sessionInfoMap.remove(sessionId);
        if (info == null) {
            return;
        }
        Long userId = info.getUserId();

        // 只在当前会话仍是映射中的会话时才移除，避免误删新连接
        WebSocketSession current = userSessions.get(userId);
        if (current != null && sessionId.equals(current.getId())) {
            userSessions.remove(userId);
        }

        // 删除 Redis 会话键
        String sessionKey = buildSessionKey(sessionId);
        redisUtil.delete(sessionKey);

        // 如用户已无其他会话，则删除在线状态键
        WebSocketSession stillOnline = userSessions.get(userId);
        if (stillOnline == null || !stillOnline.isOpen()) {
            String onlineKey = buildOnlineKey(userId);
            redisUtil.delete(onlineKey);
        }

        log.info("移除 WebSocket 会话: userId={}, sessionId={}", userId, sessionId);
    }

    public UserSessionInfo getUserInfoBySessionId(String sessionId) {
        if (sessionId == null) {
            return null;
        }
        return sessionInfoMap.get(sessionId);
    }

    public WebSocketSession getSessionByUserId(Long userId) {
        if (userId == null) {
            return null;
        }
        return userSessions.get(userId);
    }

    /**
     * 获取当前所有用户会话映射（只读视图）
     */
    public Map<Long, WebSocketSession> getAllUserSessions() {
        return Collections.unmodifiableMap(userSessions);
    }

    public void updateHeartbeat(Long userId, String sessionId) {
        if (userId == null || sessionId == null) {
            return;
        }
        UserSessionInfo info = sessionInfoMap.get(sessionId);
        if (info == null) {
            return;
        }
        Instant now = Instant.now();
        info.setLastHeartbeat(now);

        String sessionKey = buildSessionKey(sessionId);
        redisUtil.hSet(sessionKey, "last_heartbeat", String.valueOf(now.toEpochMilli()), SESSION_TTL_SECONDS);

        String onlineKey = buildOnlineKey(userId);
        redisUtil.set(onlineKey, "1", ONLINE_TTL_SECONDS);
    }

    private String buildSessionKey(String sessionId) {
        return "ws:session:" + sessionId;
    }

    private String buildOnlineKey(Long userId) {
        return "online:user:" + userId;
    }
}
